/*
 * Process
 *
 * SJSU CS 149 Spring 2009 Project
 *
 * Copyright 2009 Timmy Yee
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.List;
import java.util.LinkedList;

/**
 * The Process class describes a process that has various values known
 * collectively as the process control block (PCB). A couple of examples of
 * these values are the process ID and the priority number. This class also
 * keeps track of various timed records so that statistics can be appended
 * at the end of the simulation run.
 *
 * @author Timmy Yee
 * @author Keven Wong
 * @author Miaoer Yu
 */
public class Process
{
    // These fields are retrieved from the chosen test data file.
    // They are also known as the process control block (PCB).
    private final int id_;
    private final double arrivalTime_;
    private final double processingTime_;
    private final double probDiskRequest_;
    private final double avgDiskTime_;
    private final double probPrintRequest_;
    private final double avgPrintTime_;
    private final int priority_;
    private final int size_;

    // Used internally by the process.
    private double remainingWork_;
    private double deviceTime_;
    private MemoryManager memory_;

    // Used for statistics at the end of the simulation run.
    private double creationTime_;
    private double turnaroundTime_;
    private double responseTime_;
    private double enterTime_;
    private final List<WaitingPeriod> waitingPeriods_;
    private int switchCounter_;
    private int faultCounter_;

    /**
     * Constructs a process according to the data in the given argument.
     * <p>
     * Note that this does not create the process within the simulation.
     * Simulated creation is done by moving the process from the new queue
     * to the ready queue.
     *
     * @param data Various process control block (PCB) data,
     *             which are ints and floats encapsulated in a string.
     * @throws IllegalStateException If the data given is malformed or empty.
     */
    public Process(String[] data)
    {
        if (data.length != 8 && data.length != 9)
            throw new IllegalArgumentException();

        id_ = Integer.parseInt(data[0]);
        arrivalTime_ = Double.parseDouble(data[1]);
        processingTime_ = Double.parseDouble(data[2]);
        probDiskRequest_ = Double.parseDouble(data[3]);
        avgDiskTime_ = Double.parseDouble(data[4]);
        probPrintRequest_ = Double.parseDouble(data[5]);
        avgPrintTime_ = Double.parseDouble(data[6]);
        priority_ = Integer.parseInt(data[7]);

        /* maintain backward compatibility with phase 1 files */
        int size = 0;
        try {
            size = Integer.parseInt(data[8]);
        } catch (ArrayIndexOutOfBoundsException e) {}
        size_ = size;

        remainingWork_ = processingTime_;
        responseTime_ = Double.NEGATIVE_INFINITY;
        waitingPeriods_ = new LinkedList<WaitingPeriod>();

        switchCounter_ = 0;
        faultCounter_ = 0;
    }

    /**
     * Gets the ID of this process.
     *
     * @return The unique identification number of the process.
     */
    public int getId()
    {
        return id_;
    }

    /**
     * Gets the arrival time of this process.
     *
     * @return The absolute time that a process gets created.
     */
    public double getArrivalTime()
    {
        return arrivalTime_;
    }

    /**
     * Gets the total processing time of this process.
     *
     * @return The total time that this process has to run on the CPU.
     */
    public double getProcessingTime()
    {
        return processingTime_;
    }

    /**
     * Gets the CPU idle time for this process.
     * <p>
     * Context switching and page fault handling are considered two cases
     * where the CPU is not being utilized.
     *
     * @return The idle time.
     */
    public int getIdleTime()
    {
        int si = Processor.getContextSwitch();
        int fi = Processor.getFaultInterval();
        return switchCounter_ * si + faultCounter_ * fi;
    }

    /**
     * Gets the remaining processing time of this process.
     *
     * @return The remaining time that this process has to run on the CPU.
     */
    public double getRemainingWork()
    {
        return remainingWork_;
    }

    /**
     * Gets the priority of this process which determines
     * how important it is to getting system resources.
     *
     * @return The process priority number, where lower is better.
     */
    public int getPriority()
    {
        return priority_;
    }

    /**
     * Gets the size of this process in terms of number of pages.
     *
     * @return The size of the process.
     */
    public int getSize()
    {
        return size_;
    }

    /**
     * Checks to see if we have to do memory management (MM).
     *
     * @return True if in MM mode, false otherwise.
     */
    public boolean isMemoryEnabled()
    {
        return size_ != 0;
    }

    /**
     * Gets the allocated frames for this process.
     *
     * @return The allocated frames.
     */
    public List<Integer> getFrames()
    {
        return memory_.getFrames();
    }

    /**
     * Sets the allocated frames for this process.
     *
     * @param allocated The allocated frames.
     */
    public void setFrames(List<Integer> allocated)
    {
        memory_ = new MemoryManager(id_, size_, allocated);
    }

    /**
     * Checks to see if the process needs to load a page.
     *
     * @return True if loading a page is needed, false otherwise.
     */
    public boolean hasPageFaultOccurred()
    {
        if (isMemoryEnabled())
            return memory_.hasPageFaultOccurred();
        else
            return false;
    }

    /**
     * Gets the number of the requested page from the last page fault.
     *
     * @return The page number of the requested page.
     */
    public int getRequestedPage()
    {
        return memory_.getRequestedPage();
    }

    /**
     * Gets the inverted page table for this process.
     *
     * @return An inverted page table as a string.
     */
    public String getPageTable()
    {
        if (isMemoryEnabled())
            return memory_.getPageTable();
        else
            return "";
    }

    /**
     * Loads a page from the hard drive to the memory.
     */
    public void loadPage()
    {
        faultCounter_++;
        memory_.loadPage();
    }

    /**
     * Accesses a random page by setting the reference bit of that page.
     */
    public void accessPage()
    {
        if (isMemoryEnabled())
            memory_.accessPage();
    }

    /**
     * Computes the page faults per second for this process.
     *
     * @return The page faults per second.
     */
    public double computeFaultsPerSecond()
    {
        return faultCounter_ / (processingTime_ * 1000);
    }

    /**
     * Sets the time that this process was created.
     */
    public void setCreationTime()
    {
        creationTime_ = Simulator.getCurrentTime();
    }

    /**
     * Sets the time that this process has been alive.
     */
    public void setTurnaroundTime()
    {
        turnaroundTime_ = Simulator.getCurrentTime() - creationTime_;
    }

    /**
     * Sets the time when the process enters the ready queue.
     */
    public void setEnterTime()
    {
        enterTime_ = Simulator.getCurrentTime();
    }

    /**
     * Sets the time when the process leaves the ready queue.
     */
    public void setLeaveTime()
    {
        double leaveTime = Simulator.getCurrentTime();
        waitingPeriods_.add(new WaitingPeriod(enterTime_, leaveTime));
    }

    /**
     * Sets the time that this process waited
     * for the processor for the first time.
     */
    public void setResponseTime()
    {
        responseTime_ = Simulator.getCurrentTime() - creationTime_;
    }

    /**
     * Gets the time according to the given type from this process.
     *
     * @param t The kind of time to get.
     * @return The time recorded in this process.
     */
    public double getTime(Time t)
    {
        if (t == Time.TURNAROUND)
            return turnaroundTime_;
        else if (t == Time.WAITING) {
            double waitingTime = 0;
            for (WaitingPeriod wp : waitingPeriods_)
                waitingTime += wp.getTime();
            return waitingTime;
        } else if (t == Time.RESPONSE)
            return responseTime_;
        else
            throw new IllegalArgumentException("invalid time");
    }

    /**
     * Determines whether the process will perform a device request.
     *
     * @return a real device if there is a request, null device otherwise
     */
    public Device getDeviceRequest()
    {
        boolean d = Math.random() < probDiskRequest_;
        boolean p = Math.random() < probPrintRequest_;

        if (d && p) // if requesting both devices
            if (Math.random() < 0.5) // flip a coin
                d = false;
            else
                p = false;

        if (d)
            return Device.DISK;
        else if (p)
            return Device.PRINTER;
        else
            return null;
    }

    /**
     * Starts the disk operation by initializing how long
     * this process needs to use the disk device.
     */
    public void startDiskOperation()
    {
        deviceTime_ = Util.nearestTenth(2 * avgDiskTime_ * Math.random());
    }

    /**
     * Starts the print operation by initializing how long
     * this process needs to use the printer device.
     */
    public void startPrintOperation()
    {
        deviceTime_ = Util.nearestTenth(2 * avgPrintTime_ * Math.random());
    }

    /**
     * Makes this process wait or work on the device.
     *
     * @param time The amount of time that represents the use of device.
     */
    public void useDevice(double time)
    {
        deviceTime_ -= time;
    }

    /**
     * Gets the remaining device time of work for this process.
     *
     * @return The relative device time till process is done with I/O.
     */
    public double getDeviceTime()
    {
        return deviceTime_;
    }

    /**
     * Checks if this is the first time that the process is assigned.
     *
     * @return True if the first time, false otherwise.
     */
    public boolean hasNotBeenAssignedBefore()
    {
        return Double.isInfinite(responseTime_);
    }

    /**
     * Increments the context switch counter for this process.
     */
    public void incrementSwitchCounter()
    {
        switchCounter_++;
    }

    /**
     * Checks if the running process is done processing.
     *
     * @return True if process is done with the CPU, false otherwise.
     */
    public boolean isDone()
    {
        return Util.areDoublesEqual(remainingWork_, 0);
    }

    /**
     * Makes the running process do some processing.
     */
    public void doWork()
    {
        remainingWork_ -= Simulator.TIME_UNIT;
    }
}

